<?php

/**
 * Erra_Fields_Base_Field : Core field class
 *
 * @package
 * @author Alexis Eric
 * @copyright Copyright (c) 2012 - Erra Prod
 * @version $1.0$
 * @access public
 */
class Erra_Fields_Base_Field extends Erra_Fields_Base_Localized {

    protected $_message = null;
    protected $_namesBuilt = false;
    protected $_description;
    protected $_help;
    protected $_id = -1;
    protected $fieldData = array();
    protected $_objectClass;
    protected $_attributes = array();
    protected $_rawFieldClass = null;
    // Handles html code to display before or after field data
    protected $_prepend = null;
    protected $_append = null;
    protected $_actionName;
    // If true, the field will be shown on edit
    protected $_inAdminList = false;

    public function __construct($objectId = null) {
        parent::__construct($objectId);
        $this->_actionName = __('Save');
    }

    public function createTextSearch() {
        $objectId = 'search__' . $this->getObjectId();
        return Erra_Fields_Search_Text::create($objectId)->setOperator('=?');
    }

    public function createComboBoxSearch() {
        $objectId = 'search__' . $this->getObjectId();
        return Erra_Fields_Search_ComboBox::create($objectId)->fill($this->fieldData)->setOperator('=?');
    }

    public function createMultipleCheckBoxSearch() {
        $objectId = 'search__' . $this->getObjectId();
        return Erra_Fields_Search_MultiCheckBox::create($objectId)->fill($this->fieldData)->setOperator('=?');
    }

    // TODO : make this function recursive (not level 1 only)
    public function bErrors() {
        foreach ($this->getSubFields() as $subField) {
            if (strstr($subField->getMessage(), '[error]')) {
                return true;
            }
        }
        return false;
    }

    // TODO : make this function recursive (not level 1 only)
    public function bWarnings() {
        foreach ($this->getSubFields() as $subField) {
            if (strstr($subField->getMessage(), '[warning]')) {
                return true;
            }
        }
        return false;
    }

    /**
     * Erra_Fields_Base_Field::fill() - Fill usefull data for a field (for instance an array of key=>values for a combobox)
     *
     * @param mixed $dataArray - Fills field with data
     * @return
     */
    public function fill($dataArray) {
        $this->fieldData = $dataArray;
        return $this;
    }

    /** /
     *
     * @param type $objectId
     * @return Erra_Fields_Base_Object 
     */
    public static function create($objectId) {
        $class = get_called_class();
        return new $class($objectId);
    }

    /**
     * Erra_Fields_Base_Field::change() - Change field parameters : $aField->change('description=nouvelle description [, defaultValue=VALEUR_PAR_DEFAUT]
     *
     * @param mixed $string_or_arg_list - String or list of property to change
     * @return
     */
    public function change($string_or_arg_list) {
        if (func_num_args($string_or_arg_list) > 1) {
            $paramArray = func_get_args();
        } else {
            $paramArray = explode(',', $string_or_arg_list);
        }
        if (!is_array($paramArray))
            return null;
        $fParams = array();
        foreach ($paramArray as $param) {
            $param = explode('=', $param);
            if (!is_array($param) || count($param) != 2)
                continue;

            $fParams[trim(str_replace('EQUALS', '=', $param[0]))] = trim(str_replace('EQUALS', '=', $param[1]));
        }
        $keys = array_keys($fParams);
        // now rebuild field depending parameters
        foreach ($fParams as $propertyName => $propertyValue) {
            if (property_exists($this, $propertyName)) {
                $propertyValue = ($propertyValue == 'true' || $propertyValue == 'false') ? (boolean) $propertyValue : $propertyValue;
                if ($propertyName == 'id') {
                    $this->_id = $propertyValue;
                }
                $this->$propertyName = $propertyValue;
            }
        }
        return $this;
    }

    /**
     * edit() - Edit mode for the field
     *
     * @return A string which contains HTML code edition for this field
     */
    public function edit() {
        $res = '';
        if (is_array($this->_localizedInstances) && count($this->_localizedInstances) > 0) {
            foreach ($this->_localizedInstances as $instanceLang => $aLocalizedInstance) {
                $res .= $instanceLang . $aLocalizedInstance->edit() . '<br/>'; // br ?
            }
        } else if (is_array($this->instances) && count($this->instances) > 0) {
            foreach ($this->instances as $anInstance) {
                $res .= $anInstance->edit() . '<br/>';
            }
        } else if (is_array($this->_subFields) && count($this->_subFields) > 0) {
            foreach ($this->_subFields as $aSubField) {
                $res .= $aSubField->edit() . '<br/>';
            }
        } else {
            $res = $this->editInstance();
        }
        return $res;
    }

    /**
     * Tells if the field is a simple field (not a composite, not multiple, not internationalizable).
     *
     * @return TRUE if the field is simple, FALSE if it isn't
     */
    public function isSimpleField() {
        return ($this->isMultiple() || $this->isComposite() || $this->isLocalized()) ? false : true;
    }

    /**
     * Returns the HTML id of the field ('id' in parameter in the html anchor)
     *
     * @return A string containing the id of the field
     */
    public function getHtmlId() {
        $html_name = str_replace('[', '_', $this->_name);
        $html_name = str_replace(']', '_', $html_name);
        $html_name = str_replace('__', '_', $html_name);
        $name = trim($html_name, '_');
        return $name;
    }

    /**
     * Returns the HTML name of the field ('name' in parameter in the html anchor)
     *
     * @return A string containing the name of the field
     */
    public function getHtmlName() {
        return $this->_name;
    }

    /**
     * Internal function to set HTML name of the field
     *
     * @param A $ string containing the HTML name
     * @return
     */
    public function setHtmlName($htmlName) {
        $this->_name = $htmlName;
        return $this;
    }

    /**
     * Constructs automatically HTML names for the field
     *
     * @return
     */
    public function buildHtmlNames($arg = null) {
        if ($this->_namesBuilt == false) {
            $this->_buildHtmlNames();
            $this->_namesBuilt = true;
            return false;
        }
        return true;
    }

    /**
     * Constructs automatically HTML names for the field
     *
     * @param
     * @return
     */
    public function _buildHtmlNames() {
        if ($this->containsLocalizable(true)) {
            foreach ($this->_localizedInstances as $language => $aLocalizedInstance) {
                $aLocalizedInstance->_name = $this->getHtmlName() . '[' . $language . ']';
                $aLocalizedInstance->_buildHtmlNames();
            }
        }
        if ($this->isMultiple()) {
            foreach ($this->instances as $instanceNumber => $anInstance) {
                $anInstance->_name = $this->getHtmlName() . '[' . $instanceNumber . ']';
                $anInstance->_buildHtmlNames();
            }
        }
        if ($this->isComposite()) {
            foreach ($this->_subFields as $aSubField) {
                $aSubField->setHtmlNameParent($this->_name);
                $aSubField->_buildHtmlNames();
            }
        }
    }

    /**
     * Internal function to change the name of the field, according to a pattern
     *
     * @param mixed $value Name of the parent element
     * @return
     */
    public function setHtmlNameParent($value) {
        if (strstr($this->_name, '[') === false)
            $this->_name = $value . '[' . $this->_name . ']';
        else {
            $after_element = strstr($this->_name, '[');
            $element = str_replace($after_element, '', $this->_name);
            $this->_name = $value . '[' . $element . ']' . $after_element;
        }
    }

    /**
     * Internal function to change the name of the field, according to a pattern
     *
     * @param mixed $value Name of the child element
     * @return
     */
    public function setHtmlNameChild($value) {
        $this->_name = $this->_name . '[' . $value . ']';
    }

    /**
     * Clone a field with it's subfields and all instances
     *
     * @param mixed $source The field which has to be copied
     * @param mixed $discard_list List of subfields id separated by a pipe which may not be included in clone
     * for instance $lightUser = Erra_Fields_Base_Field::cloneField($user, 'city|address') will return an user without city and address fields
     * @return
     */
    public static function cloneField($source, $discard_list = null) {
        if (is_object($source)) {
            $discard_list = explode('|', $discard_list);
            $result = clone($source);
            $result->instances = array();
            $result->_subFields = array();
            $result->_localizedInstances = array();

            for ($i = 0; $i < count($source->instances); $i++) {
                $result->instances[$i] = Erra_Fields_Base_Field::cloneField($source->instances[$i]);
            }

            foreach ($source->_subFields as $subFieldKey => $aSubField) {
                if (!in_array($subFieldKey, $discard_list)) {
                    $result->_subFields[$subFieldKey] = Erra_Fields_Base_Field::cloneField($aSubField);
                }
            }
            foreach ($source->_localizedInstances as $language => $aLocalizedInstance) {
                $result->_localizedInstances[$language] = Erra_Fields_Base_Field::cloneField($aLocalizedInstance);
            }

            return $result;
        }
    }

    /**
     * Displays field as HTML content
     *
     * @return A string containing html code to render the string
     */
    public function toHtml($displayParameters = null) {
        return $this->getRawData();
    }

    /**
     * Cast a field to a string
     *
     * @return
     */
    public function toString() {
        return (string) $this->getRawData();
    }

    /**
     * Clear field's rawData
     *
     * @return
     */
    public function reset() {
        $this->_rawData = $this->defaultValue;
        foreach ($this->_localizedInstances as $_localizedInstances) {
            $_localizedInstances->reset();
        }
        foreach ($this->_subFields as $aSubField) {
            $aSubField->reset();
        }
        foreach ($this->instances as $anInstance) {
            $anInstance->reset();
        }
    }

    /**
     * Puts an array of data in field's data
     *
     * @param mixed $rawData A string or an array containing data well formatted
     * @return
     */
    public function setRawData($rawData) {
        if (is_array($rawData)) {
            // Format well language data from database

            foreach ($rawData as $fieldDataKey => $fieldDataValue) {
                if (Erra_Fields_Base_Field::rawDataContainsLang($fieldDataKey)) {
                    $lang = str_replace('_', '', strrchr($fieldDataKey, '_'));
                    $objectId = str_replace('___' . $lang, '', $fieldDataKey);
                    $rawData[$objectId][$lang] = $fieldDataValue;
                    unset($rawData[$fieldDataKey]);
                }
            }
        }

        $numericKey = false;
        if (is_array($rawData)) {
            $rawDataKeys = array_keys($rawData);

            $numericKey = (isset($rawDataKeys[0]) && is_numeric($rawDataKeys[0])) ? true : false;
            if (isset($rawData['id'])) {
                $this->_id = $rawData['id'];
                unset($rawData['id']);
            }
            if (isset($rawData['__message__'])) {
                $this->_message = $rawData['__message__'];
                unset($rawData['__message__']);
            }
            // IF rawData is NOT a subfield data and if depth complexity is low, its a simple data
            $depth = $this->getRawDataDepth($rawData);
            $isSubFieldData = $this->isSubFieldRawData($rawData);
            $rawData = ($depth == 1 && count($rawData) == 1 && !$isSubFieldData) ? implode('', $rawData) : $rawData;
        }

        $this->_setRawData($rawData);
        if (is_array($rawData) && Erra_Fields_Base_Field::rawDataContainsLang($rawData)) { // It's a language data
            $this->fillLocalizedFields($rawData);
        } else if (is_array($rawData) && $numericKey || $this->isMultiple()) { // It's an instance
            $this->fillInstances($rawData);
        } else if (is_array($rawData)) { // It's a subfield
            $this->fillSubFields($rawData);
        } else { // It's a simle field
            $this->_setRawData($rawData);
        }

        return $this;
    }

    /**
     * Erra_Fields_Base_Field::isSubFieldRawData() - Returns TRUE if the $data_array contains subfield's raw data
     *
     * @param mixed $data_array
     * @return
     */
    public function isSubFieldRawData($data_array) {
        if (is_array($data_array)) {
            foreach ($this->_subFields as $aSubField) {
                if (isset($data_array[$aSubField->getObjectId()])) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Erra_Fields_Base_Field::getRawDataDepth() - Returns depth of raw data (depth of the array)
     *
     * @param mixed $data_array
     * @return
     */
    public function getRawDataDepth($array) {
        $max_depth = 1;

        // IF there is only one language, we consider that depth complexity is 2
        // for language rawData eg ['my_field']['fr']
        $langs = array_keys(Erra_Core_Locale::getAvailableLangs());
        if (count($langs) == 1) {
            if (isset($array[$langs[0]])) {
                return 2;
            }
        }
        foreach ($array as $value) {
            if (is_array($value)) {
                $depth = $this->getRawDataDepth($value) + 1;
                if ($depth > $max_depth) {
                    $max_depth = $depth;
                }
            }
        }

        return $max_depth;
    }

    /**
     * Erra_Fields_Base_Field::_setRawData() - Directly affects rawData to field data
     *
     * @param mixed $rawData
     * @return
     */
    public function _setRawData($rawData) {
        $this->_rawData = $rawData;
    }

    /**
     * Returns the rawData for this field
     *
     * @param mixed $lang
     * @return
     */
    public function getRawData($lang = LANG) {
        // prevent injection by replacing all double quotes
        return $this->_getRawData($lang);
    }

    /**
     * Erra_Fields_Base_Field::_getRawData() - (internal) Returns raw data for this field
     *
     * @param mixed $lang
     * @return
     */
    public function _getRawData($lang = LANG) {
        if ($this->containsLocalizable(true) && !is_null($lang)) {
            return $this->_localizedInstances[$lang]->getRawData();
        }
        $output = security_clean_input($this->_rawData);
        return $output;
    }

    /**
     * Erra_Fields_Base_Field::getRawDataAssoc() - Returns an associative raw data from this field
     *
     * @param mixed $lang
     * @return
     */
    public function getRawDataAssoc($lang = null) {
        $result = self::getRawDataAssociative($this, $lang);
        return $result;
    }

    /**
     * Converts field's data to an associative array serializable, and field-readable
     *
     * @param mixed $field
     * @param mixed $lang
     * @return
     */
    public static function getRawDataAssociative($field, $lang = null, $forSession = null) {
        $langs = array_keys(Erra_Core_Locale::getAvailableLangs());
        $serializedArray = array();
        if (!is_null($field->_message) && !is_null($forSession))
            $serializedArray['__message__'] = $field->_message;
        // Multiple Instances ?
        if ($field->containsLocalizable(true)) {
            foreach ($langs as $language) {
                if (isset($lang) && (string) $lang == (string) $language || is_null($lang)) {
                    $serializedArray[$language] = Erra_Fields_Base_Field::getRawDataAssociative($field->getLocalizedInstanceAt($language), $lang, $forSession);
                }
            }
        } else if (count($field->getAllInstances())) {
            $phpSerializedInstances = array();
            foreach ($field->getAllInstances() as $instance) {
                if (isset($lang) && $instance->containsLocalizable() || is_null($lang)) {
                    $phpSerializedInstances[] = Erra_Fields_Base_Field::getRawDataAssociative($instance, $lang, $forSession);
                }
            }
            $serializedArray = Erra_Serialization_SerializerJson::serializeRaw($phpSerializedInstances);
            // Multiple subfields ?
        } else if (count($field->getSubFields())) {
            $subFieldArray = array();
            foreach ($field->getSubFields() as $aSubField) {
                if (isset($lang) && $aSubField->containsLocalizable() || is_null($lang)) {
                    $subFieldArray[$aSubField->getObjectId()] = Erra_Fields_Base_Field::getRawDataAssociative($aSubField, $lang, $forSession);
                }
            }
            $serializedArray = array_merge($subFieldArray, $serializedArray);
        } else {
            $rawData = (isset($lang) && $field->isLocalized() || is_null($lang)) ? $field->getRawData() : '';
            $rawData = array($rawData);
            $serializedArray = array_merge($serializedArray, $rawData);
            $serializedArray = (count($serializedArray) == 1) ? implode('', $serializedArray) : $serializedArray;
        }
        return $serializedArray;
    }

    /**
     * isDisplayable() - Returns true if field is displayable (if depth complexity is not too important)
     *
     * @return
     */
    public function isDisplayable() {
        $status = true;
        if ($this->isMultiple()) {
            if ($this->getInstanceAt(0)->isMultiple())
                $status = false;
            else if ($this->getInstanceAt(0)->isComposite())
                $status = false;
        }
        if ($this->isComposite()) {
            foreach ($this->_subFields as $aSubField) {
                if ($aSubField->isComposite() == true)
                    $status = false;
            }
        }
        return $status;
    }

    /**
     * Erra_Fields_Base_Field::__get() - Fields automagic features
     *
     * @param mixed $variableName : Can be a subfield name (eg: $book->title)
     * , an instance number (eg: $restaurants->instance_4
     * , a language value (eg: $title->fr)
     * , value of the object $title->fr->value, eventually safe for $_POST manipulation $title->fr->value
     * @return
     */
    public function __get($variableName) {
        if (self::isLocalKey($variableName) && $this->containsLocalizable(true)) {
            return $this->getLocalizedInstanceAt($variableName);
        } else if ($this->isComposite()) {
            $result = $this->getSubFieldAt($variableName);
            return $result;
        } else if ($variableName == 'value') {
            return is_string($this->getRawData(LANG)) ? trim($this->getRawData(LANG)) : '';
        } else if ($variableName == 'litteralValue') {
            return security_restore_output($this->getRawData(LANG));
        }
        return null;
    }

    /**
     * Erra_Fields_Base_Field::getValue() - Returns default value for a field
     *
     * @return
     */
    public function getValue() {
        return $this->getRawData();
    }

    /**
     * Erra_Fields_Base_Field::discard() - Retourne une instance du champ sans les sous-champs contenus dans $discard_list
     *
     * @param mixed $discard_list
     * @return
     */
    public function discard($discard_list) {
        return self::cloneField($this, $discard_list);
    }

    public function outputFieldHeader($br = true) {
        $res = '';
        if (strlen($this->getDescription())) {
            $help = (strlen($this->getHelp())) ? ' title="' . $this->getHelp() . '" ' : '';
            $helpClass = (strlen($this->getHelp())) ? ' live-tipsy' : '';
            $res .= '<span ' . $help . ' class="erra___field_description' . $helpClass . '">' . $this->getDescription() . '</span>';
            if ($br == true) {
                $res .= '<br/>';
            }
        }
        if ($this->getMessage() != null && strstr($this->_message, '[error]')) {
            $res .= '<div class="erra-form-common-field_error">' . trim(str_replace('[error]', '', $this->_message)) . '</div>';
        }
        // FIXME : we can do better graphics i think
        if ($this->getMessage() != null && strstr($this->_message, '[success]')) {
            $res .= '<span class="erra-form-common-field_success">' . trim(str_replace('[success]', '', $this->_message)) . '</span>';
        }
        if ($this->getMessage() != null && strstr($this->_message, '[info]')) {
            $res .= '<span class="erra-form-common-field_info">' . trim(str_replace('[info]', '', $this->_message)) . '</span>';
        }
        if ($this->getMessage() != null && strstr($this->_message, '[warning]')) {
            $res .= '<span class="erra-form-common-field_warning">' . trim(str_replace('[warning]', '', $this->_message)) . '</span>';
        }
        return $res;
    }

    public function getHtmlAttributes(Array $extraProperties = null) {
        $extraProperties = ($extraProperties != null && is_array($extraProperties)) ? array_merge_recursive($extraProperties, $this->getAttributes()) : $this->getAttributes();

        $properties = '';
        // Add style for error message
        if ($this->getMessage() != null && strstr($this->getMessage(), '[error]') && get_parent_class($this) != 'Erra_Fields_Base_Object') {
            if (isset($extraProperties['style'])) {
                $extraProperties['style'] .='; border:1px solid red;';
            } else {
                $extraProperties['style'] = '; border:1px solid red;';
            }
        }
        $extraProperties = is_null($extraProperties) ? array($extraProperties) : $extraProperties;
        foreach ($extraProperties as $propertyName => $propertyValue) {
            $propertyValue = is_array($propertyValue) ? implode(' ', $propertyValue) : $propertyValue;
            $propertyValue = str_replace('"', '\"', $propertyValue);
            $properties .= ' ' . $propertyName . '="' . $propertyValue . '" ';
        }
        return $properties;
    }

    /* -------------------------------------------------------------------------------------------------------
      GETTERS AND SETTERS
      -------------------------------------------------------------------------------------------------------- */

    public function setId($newId) {
        $this->_id = $newId;
        return $this;
    }

    public function getId() {
        return $this->_id;
    }

    public function idExists() {
        return ($this->_id > - 1) ? true : false;
    }

    public function setValue($value) {
        $this->setRawData($value);
        return $this;
    }

    public function getDefaultValue() {
        return $this->defaultValue;
    }

    public function setDefaultValue($defaultValue) {
        $this->defaultValue = $defaultValue;
        $this->setValue($defaultValue);
        return $this;
    }

    public function getObjectClass() {
        return get_class($this);
    }

    public function getMessage() {
        return $this->_message;
    }

    public function setMessage($msgStr) {
        $this->_message = $msgStr;
        return $this;
    }

    public function setError($errStr) {
        $this->_message = '[error] ' . $errStr;
        return $this;
    }

    public function getError() {
        return $this->_message;
    }

    public function setSuccess($errStr) {
        $this->_message = '[success] ' . $errStr;
        return $this;
    }

    public function setInfo($errStr) {
        $this->_message = '[info] ' . $errStr;
        return $this;
    }

    public function setWarning($errStr) {
        $this->_message = '[warning] ' . $errStr;
        return $this;
    }

    public function getDescription() {
        return stripslashes($this->_description);
    }

    public function setDescription($description) {
        $this->_description = $description;
        return $this;
    }

    public function getHelp() {
        return security_clean_input($this->_help);
    }

    public function setHelp($help) {
        $this->_help = $help;
        return $this;
    }

    public function isInAdminList() {
        return $this->_inAdminList;
    }

    public function setInAdminList($inAdminList) {
        $this->_inAdminList = $inAdminList;
        return $this;
    }

    public function prepend($prepend) {
        $this->_prepend = $prepend;
        return $this;
    }

    public function getPrepend() {
        return $this->_prepend;
    }

    public function append($append) {
        $this->_append = $append;
        return $this;
    }

    public function getAppend() {
        return $this->_append;
    }

    public function getFieldData() {
        return $this->fieldData;
    }

    public function setFieldData($fieldData) {
        $this->fieldData = $fieldData;
        return $this;
    }

    public function getActionName() {
        return $this->_actionName;
    }

    public function setActionName($actionName) {
        $this->_actionName = $actionName;
        return $this;
    }

    public function getRawFieldClass() {
        return $this->_rawFieldClass;
    }

    public function setRawFieldClass($_rawFieldClass) {
        $this->_rawFieldClass = $_rawFieldClass;
        return $this;
    }

    public function setAttribute($attribute_name, $attribute_value) {
        $this->_attributes[$attribute_name] = $attribute_value;
        return $this;
    }

    public function addAttribute($attribute_name, $attribute_value) {
        if (isset($this->_attributes[$attribute_name]) && strlen($this->_attributes[$attribute_name])) {
            $this->_attributes[$attribute_name] .= ' ' . $attribute_value;
        } else {
            $this->_attributes[$attribute_name] = $attribute_value;
        }
        return $this;
    }

    public function addAttributes($attributes) {
        if (is_array($attributes)) {
            foreach ($attributes as $attribute_name => $attribute_value) {
                if (strlen($attribute_name) && strlen($attribute_value)) {
                    if (isset($this->_attributes[$attribute_name]) && strlen($this->_attributes[$attribute_name])) {
                        $this->_attributes[$attribute_name] .= ' ' . $attribute_value;
                    } else {
                        $this->_attributes[$attribute_name] = $attribute_value;
                    }
                }
            }
        }
        return $this;
    }

    public function setAttributes($attributes) {
        $attributes = is_object($attributes) ? object_to_array($attributes) : $attributes;
        $this->_attributes = $attributes;
        return $this;
    }

    public function getAttributes() {
        return is_array($this->_attributes) ? $this->_attributes : array();
    }

}

?>